/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.editpolicies;

import com.architexa.org.eclipse.draw2d.IFigure;
import com.architexa.org.eclipse.draw2d.geometry.Dimension;
import com.architexa.org.eclipse.draw2d.geometry.Point;
import com.architexa.org.eclipse.draw2d.geometry.PrecisionRectangle;
import com.architexa.org.eclipse.draw2d.geometry.Rectangle;
import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.EditPolicy;
import com.architexa.org.eclipse.gef.GraphicalEditPart;
import com.architexa.org.eclipse.gef.Request;
import com.architexa.org.eclipse.gef.RequestConstants;
import com.architexa.org.eclipse.gef.commands.Command;
import com.architexa.org.eclipse.gef.commands.CompoundCommand;
import com.architexa.org.eclipse.gef.requests.AlignmentRequest;
import com.architexa.org.eclipse.gef.requests.ChangeBoundsRequest;
import com.architexa.org.eclipse.gef.requests.CreateRequest;

import java.util.List;



/**
 * For use with <code>LayoutManager</code> that require a <i>constraint</i>.
 * ConstrainedLayoutEditPolicy understands {@link RequestConstants#REQ_ALIGN_CHILDREN}
 * in addition to the Requests handled in the superclass.
 * @author hudsonr
 * @since 2.0
 */
public abstract class ConstrainedLayoutEditPolicy
	extends LayoutEditPolicy
{

/**
 * Returns the <code>Command</code> to perform an Add with the specified child and
 * constraint. The constraint has been converted from a draw2d constraint to an object
 * suitable for the model by calling {@link #translateToModelConstraint(Object)}.
 * @param child the EditPart of the child being added
 * @param constraint the model constraint, after being {@link 
 * #translateToModelConstraint(Object) translated}
 * @return the Command to add the child
 */
protected Command createAddCommand(EditPart child, Object constraint) {
	return null;
}

/**
 * The request is now made available when creating the change constraint command.  By
 * default, this method invokes the old 
 * {@link ConstrainedLayoutEditPolicy#createChangeConstraintCommand(EditPart, Object) method}.
 * 
 * @param	request		the ChangeBoundsRequest
 * @param	child		the EditPart of the child being changed
 * @param	constraint	the new constraint, after being {@link
 * 						#translateToModelConstraint(Object) translated}
 * @return	A Command to change the constraints of the given child as specified in the 
 * 			given request
 * @see	ConstrainedLayoutEditPolicy#createChangeConstraintCommand(EditPart, Object)
 * @since 3.0
 */
protected Command createChangeConstraintCommand(ChangeBoundsRequest request, 
												EditPart child, Object constraint) {
	return createChangeConstraintCommand(child, constraint);
}

/**
 * Returns the <code>Command</code> to change the specified child's constraint. The
 * constraint has been converted from a draw2d constraint to an object suitable for the
 * model
 * @param child the EditPart of the child being changed
 * @param constraint the new constraint, after being {@link
 * #translateToModelConstraint(Object) translated}
 * @return Command
 * @see #createChangeConstraintCommand(ChangeBoundsRequest, EditPart, Object)
 */
protected abstract Command createChangeConstraintCommand(
		EditPart child, Object constraint);

/**
 * A {@link ResizableEditPolicy} is used by default for children. Subclasses may 
 * override this method to supply a different EditPolicy.
 * 
 * @see com.architexa.org.eclipse.gef.editpolicies.LayoutEditPolicy#createChildEditPolicy(EditPart)
 */
protected EditPolicy createChildEditPolicy(EditPart child) {
	return new ResizableEditPolicy();
}

/**
 * Overrides <code>getAddCommand()</code> to generate the proper constraint for each child
 * being added. Once the constraint is calculated, {@link
 * #createAddCommand(EditPart,Object)} is called. Subclasses must implement this method.
 * @see com.architexa.org.eclipse.gef.editpolicies.LayoutEditPolicy#getAddCommand(Request)
 */
protected Command getAddCommand(Request generic) {
	ChangeBoundsRequest request = (ChangeBoundsRequest)generic;
	List editParts = request.getEditParts();
	CompoundCommand command = new CompoundCommand();
	command.setDebugLabel("Add in ConstrainedLayoutEditPolicy");//$NON-NLS-1$
	GraphicalEditPart childPart;
	Rectangle r;
	Object constraint;

	for (int i = 0; i < editParts.size(); i++) {
		childPart = (GraphicalEditPart)editParts.get(i);
		r = childPart.getFigure().getBounds().getCopy();
		//convert r to absolute from childpart figure
		childPart.getFigure().translateToAbsolute(r);
		r = request.getTransformedRectangle(r);
		//convert this figure to relative 
		getLayoutContainer().translateToRelative(r);
		getLayoutContainer().translateFromParent(r);
		r.translate(getLayoutOrigin().getNegated());
		constraint = getConstraintFor(r);
		/*
		 * @TODO:Pratik Should create a new createAddCommand(...) which is given the
		 * request so that attaching to/detaching from guides can be taken care of.  
		 */
		command.add(createAddCommand(childPart,
			translateToModelConstraint(constraint)));
	}
	return command.unwrap();
}

/**
 * Returns the command to align a group of children. By default, this is treated the same
 * as a resize, and {@link #getResizeChildrenCommand(ChangeBoundsRequest)} is returned.
 * @param request the AligmentRequest
 * @return the command to perform aligment
 */
protected Command getAlignChildrenCommand(AlignmentRequest request) {
	return getResizeChildrenCommand(request);
}

/**
 * Factors out RESIZE and ALIGN requests, otherwise calls <code>super</code>.
 * @see com.architexa.org.eclipse.gef.EditPolicy#getCommand(Request)
 */
public Command getCommand(Request request) {
	if (REQ_RESIZE_CHILDREN.equals(request.getType()))
		return getResizeChildrenCommand((ChangeBoundsRequest)request);
	if (REQ_ALIGN_CHILDREN.equals(request.getType()))
		return getAlignChildrenCommand((AlignmentRequest)request);

	return super.getCommand(request);
}

/**
 * Generates a draw2d constraint object derived from the specified child EditPart using
 * the provided Request. The returned constraint will be translated to the application's
 * model later using {@link #translateToModelConstraint(Object)}.
 * @param request the ChangeBoundsRequest
 * @param child the child EditPart for which the constraint should be generated
 * @return the draw2d constraint
 */
protected Object getConstraintFor (ChangeBoundsRequest request, GraphicalEditPart child) {
	Rectangle rect = new PrecisionRectangle(child.getFigure().getBounds());
	child.getFigure().translateToAbsolute(rect);
	rect = request.getTransformedRectangle(rect);
	child.getFigure().translateToRelative(rect);
	rect.translate(getLayoutOrigin().getNegated());
	return getConstraintFor(rect);
}

/**
 * Generates a draw2d constraint given a <code>Point</code>. This method is called during
 * creation, when only a mouse location is available.
 * @param point the Point relative to the {@link #getLayoutOrigin() layout origin}
 * @return the constraint
 */
protected abstract Object getConstraintFor (Point point);

/**
 * Generates a draw2d constraint given a <code>Rectangle</code>. This method is called
 * during most operations.
 * @param rect the Rectangle relative to the {@link #getLayoutOrigin() layout origin}
 * @return the constraint
 */
protected abstract Object getConstraintFor (Rectangle rect);

/**
 * Generates a draw2d constraint for the given <code>CreateRequest</code>. If the
 * CreateRequest has a size, {@link #getConstraintFor(Rectangle)} is called with a
 * Rectangle of that size and the result is returned. This is used during size-on-drop
 * creation. Otherwise, {@link #getConstraintFor(Point)} is returned.
 * <P>
 * The CreateRequest's location is relative the Viewer. The location is made
 * layout-relative before calling one of the methods mentioned above.
 * @param request the CreateRequest
 * @return a draw2d constraint
 */
protected Object getConstraintFor(CreateRequest request) {
	IFigure figure = getLayoutContainer();

	Point where = request.getLocation().getCopy();
	Dimension size = request.getSize();

	figure.translateToRelative(where);
	figure.translateFromParent(where);
	where.translate(getLayoutOrigin().getNegated());

	if (size == null || size.isEmpty())
		return getConstraintFor(where);
	else {
		//$TODO Probably should use PrecisionRectangle at some point instead of two 
		// geometrical objects
		size = size.getCopy();
		figure.translateToRelative(size);
		figure.translateFromParent(size);	
		return getConstraintFor(new Rectangle(where, size));
	}
}

/**
 * Returns the correct rectangle bounds for the new clone's location.
 * 
 * @param part the graphical edit part representing the object to be cloned.
 * @param request the changeboundsrequest that knows where to place the new
 * object.
 * @return the bounds that will be used for the new object.
 */
protected Object getConstraintForClone(GraphicalEditPart part, ChangeBoundsRequest request) {
	IFigure figure = part.getFigure();
	Rectangle bounds = new PrecisionRectangle(figure.getBounds());

	figure.translateToAbsolute(bounds);
	bounds = request.getTransformedRectangle(bounds);

	((GraphicalEditPart)getHost()).getContentPane().translateToRelative(bounds);
	bounds.translate(getLayoutOrigin().getNegated());
	return getConstraintFor(bounds);
}

/**
 * Converts a constraint from the format used by LayoutManagers,
 * to the form stored in the model.
 * @param figureConstraint the draw2d constraint
 * @return the model constraint
 */
protected Object translateToModelConstraint(Object figureConstraint) {
	return figureConstraint;
}

/**
 * Returns the <code>Command</code> to resize a group of children.
 * @param request the ChangeBoundsRequest
 * @return the Command
 */
protected Command getResizeChildrenCommand(ChangeBoundsRequest request) {
	CompoundCommand resize = new CompoundCommand();
	Command c;
	GraphicalEditPart child;
	List children = request.getEditParts();

	for (int i = 0; i < children.size(); i++) {
		child = (GraphicalEditPart)children.get(i);
		c = createChangeConstraintCommand(request, child,
			translateToModelConstraint(
				getConstraintFor(request, child)));
		resize.add(c);
	}
	return resize.unwrap();
}

/**
 * Returns the <code>Command</code> to move a group of children. By default, move is
 * treated the same as a resize.
 * @see com.architexa.org.eclipse.gef.editpolicies.LayoutEditPolicy#getMoveChildrenCommand(Request)
 */
protected Command getMoveChildrenCommand(Request request) {
	//By default, move and resize are treated the same for constrained layouts.
	return getResizeChildrenCommand((ChangeBoundsRequest)request);
}

/**
 * Returns the layout's origin relative to the {@link
 * LayoutEditPolicy#getLayoutContainer()}. In other words, what Point on the parent Figure
 * does the LayoutManager use a reference when generating the child figure's bounds from
 * the child's constraint.
 * <P>
 * By default, it is assumed that the layout manager positions children relative to the
 * client area of the layout container. Thus, when processing Viewer-relative Points or
 * Rectangles, the clientArea's location (top-left corner) will be subtracted from the
 * Point/Rectangle, resulting in an offset from the LayoutOrigin.
 * @return Point
 */
protected Point getLayoutOrigin() {
	return getLayoutContainer().getClientArea().getLocation();
}

}
